#include "omp.h"
#include <stdio.h>
#include <stdlib.h>

// Constantes
#define NUM_CHANNELS    4
#define BLOCK_SIZE      5
#define BLOCK_SQUARE    25
#define BLOCK_OFFSET    2
#define BLOCK_PADDING   4

// DEBUG
#define DEBUG           0

// Estrutura de dados
typedef unsigned char PixelChannel;
typedef short int ImageDimension;
typedef struct image {
    PixelChannel ***data;
    ImageDimension width, height;
} Image;

Image* createImage(ImageDimension width, ImageDimension height, int channels) {
    // Iteradores
    int i, j;
    // Imagem
    Image* image;

    // Alocacao da imagem
    image = (Image*) malloc(sizeof(Image));

    // Dados
    image->width = width;
    image->height = height;

    // Alocacao dos pixels da imagem
    image->data = (PixelChannel***) malloc(sizeof(PixelChannel**) * width);
    for(i = 0; i < width; i++) {
        image->data[i] = (PixelChannel**) malloc(sizeof(PixelChannel*) * height);
        for(j = 0; j < height; j++) {
            image->data[i][j] = (PixelChannel*) calloc(sizeof(PixelChannel), channels);
        }
    }

    // Retorno
    return image;
}

void deleteImage(Image *image) {
    // Iteradores
    int i, j;

    // Desalocacao
    for(i = 0; i < image->width; i++) {
        for(j = 0; j < image->height; j++) {
            free(image->data[i][j]);
        }
        free(image->data[i]);
    }
    free(image->data);
    free(image);
}

void readImage(FILE* inputFile, Image *image) {
    // Iteradores
    int i, j; // Pixels
    int k; // Canais

    // Leitura da imagem
    for(i = BLOCK_OFFSET; i < image->width - BLOCK_OFFSET; i++)
        for(j = BLOCK_OFFSET; j < image->height - BLOCK_OFFSET; j++)
            for(k = 0; k < NUM_CHANNELS; k++)
                fread(&image->data[i][j][k], sizeof(PixelChannel), 1, inputFile);
}

void writeImage(FILE* outputFile, Image *image) {
    // Iteradores
    int i, j; // Pixels
    int k; // Canais

    // Armazenamento da imagem
    for(i = BLOCK_OFFSET; i < image->width - BLOCK_OFFSET; i++)
        for(j = BLOCK_OFFSET; j < image->height - BLOCK_OFFSET; j++)
            for(k = 0; k < NUM_CHANNELS; k++)
                fwrite(&image->data[i][j][k], sizeof(PixelChannel), 1, outputFile);
}


void printImage(Image *image) {
    // Iteradores
    int i, j; // Pixels
    int k; // Canais

    for(k = 0; k < NUM_CHANNELS; k++) {
        printf("Channel %d\n", k);
        for(i = BLOCK_OFFSET; i < image->width - BLOCK_OFFSET; i++) {
            for(j = BLOCK_OFFSET; j < image->height - BLOCK_OFFSET; j++) {
                printf("%3d ", image->data[i][j][k]);
            }
            printf("\n");
        }
        printf("\n");
    }
}

void applyFilter(Image *input, Image *output) {
    // Iteradores
    int i, j; // Pixels
    int k; // Canais

    // Soma dos pixels vizinhos
    int sum;

    // Aplica o filtro
    for(i = BLOCK_OFFSET; i < input->width - BLOCK_OFFSET; i++)
        for(j = BLOCK_OFFSET; j < input->height - BLOCK_OFFSET; j++)
            for(k = 0; k < NUM_CHANNELS; k++) {
                // Soma dos pixels
                sum =
                    input->data[i-2][j-2][k] + input->data[i-2][j-1][k] + input->data[i-2][j][k] + input->data[i-2][j+1][k] + input->data[i-2][j+2][k] +
                    input->data[i-1][j-2][k] + input->data[i-1][j-1][k] + input->data[i-1][j][k] + input->data[i-1][j+1][k] + input->data[i-1][j+2][k] +
                    input->data[i][j-2][k]   + input->data[i][j-1][k]   + input->data[i][j][k]   + input->data[i][j+1][k]   + input->data[i][j+2][k]   +
                    input->data[i+1][j-2][k] + input->data[i+1][j-1][k] + input->data[i+1][j][k] + input->data[i+1][j+1][k] + input->data[i+1][j+2][k] +
                    input->data[i+2][j-2][k] + input->data[i+2][j-1][k] + input->data[i+2][j][k] + input->data[i+2][j+1][k] + input->data[i+2][j+2][k] ;

                // Media aritmetica
                output->data[i][j][k] = sum / BLOCK_SQUARE;
            }
}

// Rotina principal
int main(int argc, char* argv[]) {
    // VARIAVEIS
    Image *inputImage, *outputImage;
    ImageDimension width, height;
    FILE *inputFile, *outputFile;
    double startTime, endTime;

    // Tempo inicial
    startTime = omp_get_wtime();

    // LEITURA DOS DADOS
    inputFile = fopen("data/image.in", "rb");
    if(inputFile == NULL) {
        printf("ERRO! Arquivo invalido!\n");
        return -1;
    }

    // Dimensoes da imagem
    fread(&width, sizeof(ImageDimension), 1, inputFile);
    fread(&height, sizeof(ImageDimension), 1, inputFile);

    // Imagem de entrada
    inputImage = createImage(width + BLOCK_PADDING, height + BLOCK_PADDING, NUM_CHANNELS);
    readImage(inputFile, inputImage);
    fclose(inputFile);

    // DEBUG: Imprime a imagem de entrada
    if(DEBUG) {
        printf("INPUT IMAGE\n");
        printImage(inputImage);
    }

    // Imagem de saida
    outputImage = createImage(width + BLOCK_PADDING, height + BLOCK_PADDING, NUM_CHANNELS);

    // Aplica o smoothing
    applyFilter(inputImage, outputImage);

    // DEBUG: Imprime a imagem de saida
    if(DEBUG) {
        printf("OUTPUT IMAGE\n");
        printImage(outputImage);
    }

    // ARMAZENAMENTO DOS RESULTADOS
    outputFile = fopen("data/image.out", "wb");
    writeImage(outputFile, outputImage);
    fclose(outputFile);

    // DESALOCACAO
    deleteImage(inputImage);
    deleteImage(outputImage);

    // Tempo final
    endTime = omp_get_wtime();

    // Tempo de execucao
    printf("time: %f\n", endTime - startTime);

    // Retorno
    return 0;
}
